﻿using System;
using System.Drawing;
using System.IO;
using System.Net;
using System.Reflection;
using System.Security.Principal;
using System.Threading;
using System.Web;
using Common.Logging;
using HttpWatch;
using NPMonitor.Attributes;
using NPMonitor.DataModel;
using NPMonitor.Interfaces;
using NPMonitor.Utility;
using OpenQA.Selenium;
using OpenQA.Selenium.Chrome;
using OpenQA.Selenium.Firefox;
using OpenQA.Selenium.IE;

namespace NPMonitor.Utility
{
    [Job]
    [Serializable]
    public abstract class WebJobBase : JobBase
    {
        #region Members and Properties

        private bool disposed = false;

        private ILog log = LogManager.GetCurrentClassLogger();
        protected Controller Control = new Controller();
        private IWebDriver web;


        protected IWebDriver WebDriver
        {
            get { return web; }
        }

        protected JobExecutionResult ExecutionResult
        {
            get;
            private set;
        }

        public static Setting Setting
        {
            get
            {
                return SettingFactory.Create();
            }
        }

        public static int DefaultTimeoutSecs
        {
            get
            {
                if (Setting.TimeoutSecs == 0)
                    return -1;
                else
                    return Setting.TimeoutSecs;
            }
        }

        protected int MachinePoolId
        {
            get { return Setting.PoolId; }
        }

        #endregion

        #region De/Constructors

        public WebJobBase()
        {
            ExecutionResult = new JobExecutionResult()
            {
                ResultStatus = JobResultStatus.Success
            };
        }

        ~WebJobBase()
        {
            this.Dispose(false);
        }

        #endregion

        #region JobBase Members

        protected virtual void Init()
        {
            WindowsIdentity.GetCurrent().Impersonate();

            if (Configuration.ConfigurationSettings.SettingsProvider.ClearEnvironmentAtStart == true)
            {
                ClearEnvironment();
            }
        }

        protected virtual void ClearEnvironment()
        {
            // Check and close all opened browsers
            WebDriverHelper.ForceQuitWebDriver(Setting.BrowserType);
        }

        public override JobExecutionResult Execute(string jobData, Guid historyUniqueId)
        {
            Init();

            LogManager.GetCurrentClassLogger().Debug("Job Execution Start -----------------------------------------------------------");

            Plugin plugin = null;

            try
            {
                LogManager.GetCurrentClassLogger().Debug("WebJobBase - Before - web = WebDriverFactory.GetWebDriver(Setting.BrowserType);");

                web = WebDriverFactory.GetWebDriver(Setting.BrowserType);

                LogManager.GetCurrentClassLogger().Debug("WebJobBase - After - web = WebDriverFactory.GetWebDriver(Setting.BrowserType);");

                LogManager.GetCurrentClassLogger().Debug("WebJobBase - Before -  plugin = AttachWeb();");

                // Attach HttpWatch to the instance of IE/Firefox created through Selenium
                plugin = AttachWeb();
                // Start recording now that page containing the form is loaded
                plugin.Log.EnableFilter(false);
                plugin.Clear();
                plugin.Record();

                LogManager.GetCurrentClassLogger().Debug("WebJobBase - Before -  ExecuteJob(plugin);");

                web.Manage().Window.Maximize();
                ExecuteJob(plugin);

                LogManager.GetCurrentClassLogger().Debug("WebJobBase - After -  ExecuteJob(plugin);");
            }
            catch (WebException ex)
            {
                LogManager.GetCurrentClassLogger().Debug("WebJobBase - In Start -  catch (WebException ex)");

                if (ex.Status == WebExceptionStatus.ProtocolError)
                {
                    ExecutionResult.ErrorCode = ((HttpWebResponse)ex.Response).StatusCode.ToString();
                }

                ExecutionResult.ErrorMessage = ex.Message;
                ExecutionResult.ResultStatus = JobResultStatus.Fail;

                LogManager.GetCurrentClassLogger().Debug("WebJobBase - Out Start -  catch (WebException ex)");
            }
            catch (ThreadAbortException ex)
            {
                LogManager.GetCurrentClassLogger().Debug("WebJobBase - In Start -  catch (ThreadAbortException ex)");

                ExecutionResult.ErrorMessage = ex.Message;
                ExecutionResult.ResultStatus = JobResultStatus.Fail;

                try
                {
                    //超时异常，错误截图
                    ErrorScreenshot(Setting.JobScreenshotTimeoutFileFolder, jobData);
                }
                catch { }

                LogManager.GetCurrentClassLogger().Debug("WebJobBase - Out Start -  catch (ThreadAbortException ex)");

            }
            catch (Exception ex)
            {
                LogManager.GetCurrentClassLogger().Debug("WebJobBase - In Start -  catch (Exception ex)");

                ExecutionResult.ErrorMessage = ex.Message;
                if (ex.InnerException != null)
                {
                    ExecutionResult.ErrorMessage = ex.Message + " -----> " + ex.InnerException.Message;
                }
                ExecutionResult.ResultStatus = JobResultStatus.Fail;

                try
                {
                    //其它异常，错误截图
                    ErrorScreenshot(Setting.JobScreenshotOtherFileFolder, jobData);
                }
                catch { }

                LogManager.GetCurrentClassLogger().Debug("WebJobBase - Out Start -  catch (Exception ex)");
            }
            finally
            {
                if (plugin != null)
                {
                    try
                    {
                        LogManager.GetCurrentClassLogger().Debug("WebJobBase - Before -  plugin.Stop();");

                        plugin.Stop();

                        LogManager.GetCurrentClassLogger().Debug("WebJobBase - Before -  LogToDB(plugin);");

                        LogToDB(plugin, historyUniqueId);
                        LogManager.GetCurrentClassLogger().Debug("WebJobBase - End -  LogToDB(plugin);");
                    }
                    catch { }
                }

                this.Dispose(true);
            }

            LogManager.GetCurrentClassLogger().Debug("WebJobBase - Before - ExecutionResult");


            LogManager.GetCurrentClassLogger().Debug("Job Execution End -----------------------------------------------------------");

            return ExecutionResult;
        }

        protected override void Dispose(bool disposing)
        {
            if (!this.disposed)
            {
                try
                {
                    // If disposing equals true, dispose all managed and unmanaged resources
                    if (disposing)
                    {
                        // Release managed resources
                    }

                    // Release the native unmanaged resources
                    QuitWebBrowser();

                    QuitWebDriver();
                }
                finally
                {
                    base.Dispose(disposing);
                }
            }
        }

        #endregion

        #region Private Member

        protected abstract void ExecuteJob(Plugin plugin);

        private Plugin AttachWeb()
        {
            string uniqueTitle = Guid.NewGuid().ToString();
            IJavaScriptExecutor js = web as IJavaScriptExecutor;
            js.ExecuteScript("document.title = '" + uniqueTitle + "';");
            string browserType = js.ExecuteScript("return navigator.userAgent;").ToString();

            if (browserType.Contains("MSIE"))
            {
                return Control.IE.Attach(IEHelper.FindIEBrowser(uniqueTitle));
            }
            else if (browserType.Contains("Firefox"))
            {
                return Control.Firefox.Attach(WebDriverFactory.FirefoxProfile.ProfileDirectory);
            }

            return null;
        }

        private void LogToDB(Plugin plugin, Guid historyUniqueId)
        {
            if (plugin.Log.Pages.Count >= 0)
            {
                foreach (HttpWatch.Page page in plugin.Log.Pages)
                {
                    try
                    {
                        Console.WriteLine("\r\nPage Title: '" + page.Title + "'");

                        //page.s.Entries.Summary.
                        int actionId = HttpActionSave(page, historyUniqueId);
                        foreach (HttpWatch.Entry entry in page.Entries)
                        {
                            try
                            {
                                HttpActionRequestSave(entry, actionId);
                            }
                            catch (Exception ex)
                            {
                                log.Error(ex.Message);
                                continue;
                            }
                        }
                    }
                    catch (Exception ex)
                    {
                        log.Error(ex.Message);
                        continue;
                    }
                }
            }
        }

        private int HttpActionSave(HttpWatch.Page page, Guid historyUniqueId)
        {
            string url = string.Empty;
            if (page.Entries.Count > 0 && page.Entries[0] != null)
                url = page.Entries[0].URL;

            string scenarioNO = string.Empty;

            try
            {
                scenarioNO = page.Comment;
            }
            catch { }

            Summary summary = page.Entries.Summary;
            HttpActions action = new HttpActions()
            {
                Started = page.StartedSecs,
                BeginTime = page.StartedDateTime.ToUniversalTime(),
                Time = summary.Time,
                Url = url,
                Title = page.Title,
                SendBytes = summary.BytesSent,
                ReceivedBytes = summary.BytesReceived,
                ErrorCount = summary.Errors.Count,
                PoolId = Setting.PoolId,
                MachineName = Setting.MachineName,
                ScenarioNO= scenarioNO,
                HistoryUniqueId = historyUniqueId
            };
            return action.Insert();
        }

        private void HttpActionRequestSave(HttpWatch.Entry entry, int actionId)
        {
            HttpActionRequests request = new HttpActionRequests()
            {
                ActionId = actionId,
                URL = entry.URL,
                Method = entry.Method,
                Result = entry.Result,
                Error = entry.Error,
                ServerIP = entry.ServerIP,
                ServerPort = entry.ServerPort.ToString(),
                Started = entry.Started,
                BeginTime = entry.StartedDateTime.ToUniversalTime(),
                StatusCode = entry.StatusCode,
                Time = entry.Time,
                SendBytes=entry.BytesSent,
                ReceivedBytes = entry.BytesReceived,
                Blocked = entry.Timings.Blocked.Duration,
                CacheRead = entry.Timings.CacheRead.Duration,
                Connect = entry.Timings.Connect.Duration,
                DNSLookup = entry.Timings.DNSLookup.Duration,
                Network = entry.Timings.Network.Duration,
                Receive = entry.Timings.Receive.Duration,
                Send = entry.Timings.Send.Duration,
                TTFB = entry.Timings.TTFB.Duration,
                Wait = entry.Timings.Wait.Duration
            };
            request.Insert();
        }

        private void QuitWebBrowser()
        {
            Thread thread = new Thread(() =>
            {
                if (web != null)
                {
                    try
                    {
                        LogManager.GetCurrentClassLogger().Debug("WebJobBase - Before -  web.Close();");

                        web.Close();

                        LogManager.GetCurrentClassLogger().Debug("WebJobBase - After -  web.Close();");
                    }
                    catch
                    {
                        LogManager.GetCurrentClassLogger().Debug("WebJobBase - In - web.Close(); catch");
                    }
                }
            });

            thread.Start();

            bool completedWithoutTimeout = thread.Join(10 * 1000);

            if (!completedWithoutTimeout)
            {
                WebDriverHelper.ForceQuitWebBrowser(Setting.BrowserType);
                // thread.Abort();

                while (thread.IsAlive)
                {
                    Thread.Sleep(1);
                }
            }
        }

        /// <summary>
        /// If QuitWebDriver exceed timeout, will invoke force QuitWebDriver
        /// </summary>
        /// <param name="timeout"></param>
        private void QuitWebDriver()
        {
            Thread thread = new Thread(() =>
            {
                if (web != null)
                {
                    try
                    {
                        LogManager.GetCurrentClassLogger().Debug("WebJobBase - Before - web.Quit();");

                        web.Quit();

                        LogManager.GetCurrentClassLogger().Debug("WebJobBase - After - web.Quit();");
                    }
                    catch
                    {
                        LogManager.GetCurrentClassLogger().Debug("WebJobBase - In - web.Quit(); catch");
                    }
                }
                else
                {
                    // Sometime webdriver crash when create, web will be null, but webdriver has already been created
                    // so check and terminate here
                    WebDriverHelper.ForceQuitWebDriver(Setting.BrowserType);
                }
            });

            thread.Start();

            bool completedWithoutTimeout = thread.Join(10 * 1000);

            if (!completedWithoutTimeout)
            {
                WebDriverHelper.ForceQuitWebDriver(Setting.BrowserType);
                // thread.Abort();

                while (thread.IsAlive)
                {
                    Thread.Sleep(1);
                }
            }
        }

        /// <summary>
        /// job 失败后，错误截图
        /// </summary>
        /// <param name="subFileFolder"></param>
        /// <param name="jobData"></param>
        private void ErrorScreenshot(string subFileFolder, string jobData)
        {
            LogManager.GetCurrentClassLogger().Debug("ErrorScreenshot(string subFileFolder, string jobData) -- Before");

            //截图，并保存路径
            string screenshotPath = Path.Combine(System.Environment.CurrentDirectory, SettingFactory.Create().JobScreenshotFileFolder);

            if (!Directory.Exists(screenshotPath))
            {
                Directory.CreateDirectory(screenshotPath);
            }

            string subPath = Path.Combine(screenshotPath, subFileFolder);
            if (!Directory.Exists(subPath))
            {
                Directory.CreateDirectory(subPath);
            }

            string yearFileFolder = DateTime.UtcNow.Year.ToString();
            string yearPath = Path.Combine(subPath, yearFileFolder);
            if (!Directory.Exists(yearPath))
            {
                Directory.CreateDirectory(yearPath);
            }

            string monthFileFolder = DateTime.UtcNow.Month.ToString();
            string monthPath = Path.Combine(yearPath, monthFileFolder);
            if (!Directory.Exists(monthPath))
            {
                Directory.CreateDirectory(monthPath);
            }

            string filePath = Path.Combine(monthPath, jobData + ".jpeg");
            
            Screenshot screenshot = ((ITakesScreenshot)web).GetScreenshot();
            LogManager.GetCurrentClassLogger().Debug("Screenshot screenshot = ((ITakesScreenshot)web).GetScreenshot();");

            screenshot.SaveAsFile(filePath, System.Drawing.Imaging.ImageFormat.Jpeg);

            LogManager.GetCurrentClassLogger().Debug("ErrorScreenshot(string subFileFolder, string jobData) -- After");
        }

        #endregion
    }
}
